import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.LinkedList;
import java.util.Random;


/**
 * @author Leonardo Marmol, Manuel Varela, Franco Castellina.
 * 
 * @brief Clase que contiene los metodos para crear una simulacion de clientes
 * que toman un servicio, teniendo en cuenta el tiempo de arribo del cliente y el tiempo de servicio
 * de cada uno de los clientes.
 * 
 */

public class Simulacion {
	private static double tiempoLimite;
	private static double sim_time;
	private static double cantEntradas;
	private static double cantSalida;
	private static double cantEntradasGeneradas;
	
	private static double tClienteEnSistema;
	private static double tPromClienteEnSistema;
	private static double tiempoClienteEnCola;
	private static double tiempoPromedioClienteEnCola;
	private static double longitudCola;
	private static double longitudPromedioCola;
	private static double cantidadClientes;
	private static double cantidadPromedioClientes;
	private static double utilizacionDelServidor;
	
	private static LinkedList<Servidor> servidores;
	private static LinkedList<Cliente> colaClientes;
	private static LinkedList<Cliente> listaClientes;
	private String distribucion;
	private static double semilla;
	private static double[] listaEventos;
	
	
	/**
	 * inicializa la simulacion con los parametros de entrada.
	 * 
	 */
	public Simulacion(int cantServidores){
		borrarPromedioClienteEnSistema();
		servidores = new LinkedList<Servidor>();
		colaClientes = new LinkedList<Cliente>();
		listaClientes= new LinkedList<Cliente>();
		while (cantServidores>0){
			Servidor servidor = new Servidor();
			servidores.add(servidor);
			cantServidores--;
		}
		listaEventos = new double[2];
		utilizacionDelServidor = 0;
		cantEntradas = 0;
		cantSalida = 0;
		tClienteEnSistema = 0;
		tiempoClienteEnCola = 0;
		longitudCola = 0;
		cantidadClientes = 0;
		
	}
	
	/**
	 * Inicializa los valores correspondientes para la simulacion.
	 *  
	 */
	static void inicializar(){
		sim_time = 0;
		listaEventos[0]= ((Cliente) listaClientes.getFirst()).gettInterArribo();//arribo del primer cliente.
		listaEventos[1]= Float.MAX_VALUE;//asegura que el 1 evento sea entrada.
	}
	
	/**
	 * Determina el tipo del proximo evento. 
	 * avanza el reloj. 
	 */
	void timing(){
		cantEntradas = 0;
		while ((sim_time<tiempoLimite ) && (cantEntradasGeneradas>cantSalida)){
			if (listaEventos[0]<listaEventos[1]){
				sim_time = listaEventos[0];  
				arribo();
				
			}
			else{
				if (listaEventos[0]>listaEventos[1]){    
					sim_time = listaEventos[1];
					salida();
					if ((cantSalida % 10) ==0){
						escribirPromedioClienteEnSistema();
					}
				}
				else{
					cantSalida++;
				}
			}
		}
		System.out.println("entradas"+cantEntradas);
		System.out.println("entradasgeneradas"+cantEntradasGeneradas);
		System.out.println("salidas"+cantSalida);
		System.out.println("servidor"+servidores.getFirst().getTiempoOcupado());
		
		System.out.println("tiempo cliente en sistema: "+tClienteEnSistema);
		System.out.println("tiempo cliente en cola: "+tiempoClienteEnCola);
		System.out.println("SIM TIME: " +sim_time);
		
		utilizacionDelServidor=utilizacionDelServidor/sim_time;
		tPromClienteEnSistema=tClienteEnSistema/cantEntradas;
		tiempoPromedioClienteEnCola=tiempoClienteEnCola/cantEntradas;
		longitudPromedioCola=longitudCola/sim_time;
		cantidadPromedioClientes=cantidadClientes/sim_time;
		escribirSalida();
		//escribirPromedioClienteEnSistema();
	}
	
	/**
	 * Retorna el proximo servidor libre.
	 * @return res 
	 */
	private static Servidor proximoServidorLibre(){
		Servidor res = new Servidor();
		res.setTiempoSalida(Integer.MAX_VALUE);
		for (int i = 0; i < servidores.size(); i++) {
			if (servidores.get(i).ocupado()){
				if (((Servidor) servidores.get(i)).getTiempoSalida()<= res.getTiempoSalida()){
					res = (Servidor) servidores.get(i);
				}
			}
		}
		return res;
	}
	
	private static int cantServidoresOcupados(){
		int res = 0;
		for (int i = 0; i < servidores.size(); i++) {
			if (((Servidor) servidores.get(i)).ocupado()){
				res++;
			}
		}
		return res;
	}
	
	/**
	 * Calculo del fin de servicio.
	 * Chequea si algun servidor esta libre.
	 * Actualiza estadistica.
	 * 
	 */
	private static void arribo(){
		boolean atendido = false;
		if (!listaClientes.isEmpty()){
			cantEntradas++;
			Cliente cliente = new Cliente();
			cliente = listaClientes.getFirst();
			utilizacionDelServidor = cliente.gettServicio()+utilizacionDelServidor;
			cliente.settArribo(sim_time);
			for (int i = 0; i < servidores.size() && !atendido; i++) {
				if (!((Servidor) servidores.get(i)).ocupado()){
					atendido = true;
					((Servidor) servidores.get(i)).cambiarEstado();
					((Servidor) servidores.get(i)).setCliente(cliente);
					((Servidor) servidores.get(i)).setTiempoSalida(sim_time+cliente.gettServicio());
					servidores.get(i).setTiempoOcupado(servidores.get(i).getTiempoOcupado()+cliente.gettServicio());
				}
			}
			listaEventos[1]= proximoServidorLibre().getTiempoSalida();
			listaClientes.removeFirst();
			if (!listaClientes.isEmpty()){
				listaEventos[0]=listaClientes.getFirst().gettInterArribo()+sim_time;
			}else{
				listaEventos[0]=Integer.MAX_VALUE;
			}
			if (!atendido){
				cliente.settEnCola(sim_time);
				colaClientes.add(cliente);
			}
			if (listaEventos[0]<listaEventos[1]){
				cantidadClientes = cantidadClientes +  ((colaClientes.size()+cantServidoresOcupados())* (listaEventos[0]-sim_time));
				longitudCola = longitudCola + (colaClientes.size()* (listaEventos[0]-sim_time));
			}
			else{
				cantidadClientes = cantidadClientes +  ((colaClientes.size()+cantServidoresOcupados())* (listaEventos[1]-sim_time));
				longitudCola = longitudCola + (colaClientes.size()* (listaEventos[1]-sim_time));
			}
		}
		else{
			listaEventos[0]=Integer.MAX_VALUE;
		}
	}
	
	/**
	 * Calculo del fin de servicio.
	 * Chequea si algun servidor esta libre.
	 * Ocupa un servidor si hay en la cola.
	 * 
	 */
	private static void salida(){
		boolean desocupo = false;
		
		for (int i = 0; i < servidores.size() && !desocupo; i++) {
			if (((Servidor) servidores.get(i)).ocupado()){
				if (((Servidor) servidores.get(i)).getTiempoSalida()==sim_time){
					((Servidor) servidores.get(i)).cambiarEstado();
					tClienteEnSistema = ((sim_time-((Servidor) servidores.get(i)).getCliente().gettArribo()))+tClienteEnSistema;
					cantSalida++;
					desocupo = true;
					if (!colaClientes.isEmpty()){
						((Servidor) servidores.get(i)).cambiarEstado();
						tiempoClienteEnCola = (sim_time-colaClientes.getFirst().gettEnCola())+tiempoClienteEnCola;
						((Servidor) servidores.get(i)).setCliente(colaClientes.getFirst());
						servidores.get(i).setTiempoOcupado(servidores.get(i).getTiempoOcupado()+colaClientes.getFirst().gettServicio());
						((Servidor) servidores.get(i)).setTiempoSalida(sim_time+colaClientes.removeFirst().gettServicio());
						listaEventos[1]= proximoServidorLibre().getTiempoSalida();
					}else{
						listaEventos[1]= Integer.MAX_VALUE;
					}					
				}
			}	
		}
		if (listaEventos[0]<listaEventos[1]){
			cantidadClientes = cantidadClientes +  ((colaClientes.size()+cantServidoresOcupados())* (listaEventos[0]-sim_time));
			longitudCola = longitudCola + (colaClientes.size()* (listaEventos[0]-sim_time));
		}
		else{
			cantidadClientes = cantidadClientes +  ((colaClientes.size()+cantServidoresOcupados())* (listaEventos[1]-sim_time));
			longitudCola = longitudCola + (colaClientes.size()* (listaEventos[1]-sim_time));
		}
	}
	
	public double congruenciaLineal(Double semilla){
		return (((1103515425*semilla)+12345)%(Math.pow(2, 32)));
	}
	
	public double random(double semilla){
		return Math.abs(semilla/Math.pow(2, 32));
	}
	
	/**
	 * Genera los valores aleatorios de tiempo de entrada de un cliente y la demora del servicio
	 * que se le realizara en forma exponencial.
	 */
	@SuppressWarnings("static-access")
	public Double generaAleatorioExponencial(Double media){
		semilla = congruenciaLineal(semilla);
		return (-1)*(1/(1/media))*Math.log(random(semilla));
	}
	
	/**
	 * Genera los valores aleatorios de tiempo de entrada de un cliente y la demora del servicio
	 * que se le realizara en forma exponencial.
	 */
	@SuppressWarnings("static-access")
	public Double generaAleatorioUniforme(Double a,Double b){
		semilla = congruenciaLineal(semilla);
		return random(semilla)*(b-a)+a;
	}
	
	/**
	 * Genera los valores aleatorios de tiempo de entrada de un cliente y la demora del servicio
	 * que se le realizara en forma exponencial.
	 */
	@SuppressWarnings("static-access")
	public Double generaAleatorioTriangular(Double a,Double b,Double c){
		semilla = congruenciaLineal(semilla);
		if (semilla<a && semilla>b) {
			return Math.sqrt(random(semilla)*(b-a)*(c-a))+a;
		}else{
			return c- Math.sqrt(random(semilla)*(b-a)*(c-a));
		}
	}
	
		
	/**
	 * Genera los valores aleatorios de tiempo de entrada de un cliente y la demora del servicio
	 * que se le realizara en forma Uniforme.
	 */
	@SuppressWarnings("static-access")
	public void generarClientesAleatorios(Double mediaArribo,Double a,Double b,Double c,int tipoArribo,
			                                Double mediaServicio,Double A,Double B, Double C,int tipoServicio,
			                                Double tiempoLimite, Double cantidadClientes){
		this.tiempoLimite = tiempoLimite;
		Double totalTiempoArribo = new Double(0);
		semilla= new Double(127);
		int contadorClientes = 0;
		while (totalTiempoArribo<tiempoLimite && cantidadClientes > contadorClientes) { 
			contadorClientes++;
			Cliente cliente = new Cliente();
			switch (tipoArribo) {
			case 0:
				cliente.settInterArribo(generaAleatorioExponencial(mediaArribo));
				break;
			case 1:
				cliente.settInterArribo(generaAleatorioUniforme(a,b));
				break;
			case 2:
				cliente.settInterArribo(generaAleatorioTriangular(a,b,c));
				break;
			}
			switch (tipoServicio) {
			case 0:
				cliente.settServicio(generaAleatorioExponencial(mediaServicio));
				break;
			case 1:
				cliente.settServicio(generaAleatorioUniforme(A,B));
				break;
			case 2:
				cliente.settServicio(generaAleatorioTriangular(A,B,C));
				break;
			}
			totalTiempoArribo = totalTiempoArribo + cliente.gettInterArribo();
			listaClientes.add(cliente);
		}
		cantEntradasGeneradas = contadorClientes;
	}	
	
	/**
	 * @return the tiempoLimite
	 */
	public static double getTiempoLimite() {
		return tiempoLimite;
	}

	/**
	 * @return the tPromClienteEnSistema
	 */
	public double gettPromClienteEnSistema() {
		return tPromClienteEnSistema;
	}

	/**
	 * @return the tPromClienteEnCola
	 */
	public double gettPromClienteEnCola() {
		return tiempoPromedioClienteEnCola;
	}

	/**
	 * @return the utilizacionServidor
	 */
	public double getUtilizacionDelServidor() {
		return utilizacionDelServidor;
	}

	/**
	 * @return the longPromedioCola
	 */
	public double getLongPromedioCola() {
		return longitudPromedioCola;
	}

	/**
	 * @return the distribucion
	 */
	public String getDistribucion() {
		return distribucion;
	}
	
	/**
	 * @return the cantidadPromedioClientes
	 */
	public static double getCantidadPromedioClientes() {
		return cantidadPromedioClientes;
	}

	/**
	 * @param distribucion the distribucion to set
	 */
	public void setDistribucion(String distribucion) {
		this.distribucion = distribucion;
	}

	/**
	 * Escribe en un archivo de texto llamado "tiempoPromedio.dat" el valor calculado cada 10 peesonas 
	 * de el tiempo promedio que pasa un cliente en el sistema. Si el archivo no existe, lo genera.
	 */
	public static void escribirPromedioClienteEnSistema(){
		try{
			FileWriter fw = new FileWriter("tiempoPromedio.dat",true);
			BufferedWriter bw = new BufferedWriter(fw);
			PrintWriter promSalida = new PrintWriter(bw);
			promSalida.println(tClienteEnSistema/cantEntradas);
			promSalida.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Borra, de existir, el archivo "tiempoPromedio.dat" para evitar acumular
	 *  resultados de corridas anteriores.
	 */
	public static void borrarPromedioClienteEnSistema(){
		File archivoPromedio = new File ("tiempoPromedio.dat");
		archivoPromedio.delete();
	}
	
	/**
	 * Escribe en un archivo de texto llamado "salida.dat" el valor de las variables de performance
	 * al terminar la simulacion. Si el archivo no existe, lo genera.
	 */
	public static void escribirSalida(){
		try{
			FileWriter fw = new FileWriter("salida.dat",true);
			BufferedWriter bw = new BufferedWriter(fw);
			PrintWriter salida = new PrintWriter(bw);
			salida.println(utilizacionDelServidor+" "+ tPromClienteEnSistema+" "+tiempoPromedioClienteEnCola+" "+cantidadPromedioClientes+" "+longitudPromedioCola);
			System.out.println("En el archivo se imprime en el siguiente orden: 'Utilizacionn del Servidor'-'Tiempo promedio cliente en sistema'-'Tiempo promedio cliente en cola'-'Cantidad Promedio de clientes'-'longitud promedio de cola'");
			salida.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new GUIDatosSimulacion();
	}

}
